#include "utils.h"

#include "clang/Lex/Preprocessor.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Parse/ParseAST.h"
#include "clang/AST/Mangle.h"

namespace pdxcxx
{
	ClangParser::ClangParser()
	{
		// Usually all examples try to build the CompilerInstance Object from CompilerInvocation object.
		// However, CompilterInstance already has an in-built CompilerInvocation object. So we use it.
		// Only problem is: target options are not set correctly for the in-built object. We do it manually.
		// This below line is just to assert that the object exist.
		clang::CompilerInvocation& invocation = m_CompilerInstance.getInvocation();
		
		// Diagnostics is needed - set it up
		m_CompilerInstance.createDiagnostics();
		m_CompilerInstance.getDiagnostics().setSuppressSystemWarnings(true);

		// set the include directories path - you can configure these at run-time from command-line options
		clang::HeaderSearchOptions & headerSearchOptions = m_CompilerInstance.getHeaderSearchOpts();
		headerSearchOptions.AddPath("C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\include", clang::frontend::IncludeDirGroup::CXXSystem, false, false);
		//headerSearchOptions.AddSystemHeaderPrefix("C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\include", true);

		// set few options
		clang::LangOptions& langOptions = m_CompilerInstance.getLangOpts();
		langOptions.CPlusPlus = 1;
		langOptions.CPlusPlus11 = 1;
		langOptions.Bool = 1;
		langOptions.RTTI = 0;
		langOptions.Exceptions = 1;
		//m_CompilerInstance.getDiagnostics().setErrorsAsFatal(false);
#if defined(_MSC_VER)
		//diag::warn_cxx_ms_struct
		langOptions.MicrosoftExt = 1;
		langOptions.MSVCCompat = 1;
		langOptions.MSBitfields = 1;
		langOptions.DelayedTemplateParsing = 1; // MSVC parses templates at the time of actual use
		m_CompilerInstance.getDiagnosticOpts().setFormat(clang::TextDiagnosticFormat::Msvc);
		//clInstance.getTargetOpts().ABI = "microsoft";
#endif

		// Need to set the source manager before AST
		m_CompilerInstance.createFileManager();
		m_CompilerInstance.createSourceManager(m_CompilerInstance.getFileManager());
		
		// Prepare for next file and to ensure context is always available.
		prepareDefaults();
	}

	ClangParser::~ClangParser()
	{
		//necessary or we get link issues due to other files generating destructors with unique_ptr
	}

	void ClangParser::prepareDefaults()
	{
		// Need to set the target before AST. Adjust the default target options and create a target
		m_CompilerInstance.getTargetOpts().Triple = llvm::sys::getProcessTriple();
		std::shared_ptr<clang::TargetOptions> TargetOpts(new clang::TargetOptions(m_CompilerInstance.getTargetOpts()));
		m_CompilerInstance.setTarget(clang::TargetInfo::CreateTargetInfo(m_CompilerInstance.getDiagnostics(), TargetOpts));

		// Create pre-processor and AST Context
		m_CompilerInstance.createPreprocessor(clang::TranslationUnitKind::TU_Module);
		if (m_CompilerInstance.hasPreprocessor())
		{
			clang::Preprocessor & preprocessor = m_CompilerInstance.getPreprocessor();
			preprocessor.getBuiltinInfo().InitializeBuiltins(preprocessor.getIdentifierTable(), preprocessor.getLangOpts());
		}

		m_CompilerInstance.createASTContext();
	}

	void ClangParser::prepareCompilerforFile(const char* szSourceCodeFilePath)
	{
		// supply the file to the source manager and set it as main-file
		const clang::FileEntry * file = m_CompilerInstance.getFileManager().getFile(szSourceCodeFilePath);
		clang::FileID fileID = m_CompilerInstance.getSourceManager().createFileID(file, clang::SourceLocation(), clang::SrcMgr::CharacteristicKind::C_User);
		m_CompilerInstance.getSourceManager().setMainFileID(fileID);

		// CodeGenAction needs this
		clang::FrontendOptions& feOptions = m_CompilerInstance.getFrontendOpts();
		feOptions.Inputs.clear();
		feOptions.Inputs.push_back(clang::FrontendInputFile(szSourceCodeFilePath, clang::FrontendOptions::getInputKindForExtension(clang::StringRef(szSourceCodeFilePath).rsplit('.').second), false));
	}

	std::unique_ptr<clang::ASTContext> ClangParser::parseAST(const char* szSourceCodeFilePath, clang::ast_matchers::MatchFinder* finder)
	{
		// create the compiler instance setup for this file as main file
		prepareCompilerforFile(szSourceCodeFilePath);

		std::unique_ptr<clang::ASTConsumer> astConsumer;
		if (finder)
		{
			astConsumer = std::move(finder->newASTConsumer());
		}
		else
		{
			//dummy
			astConsumer = std::unique_ptr<clang::ASTConsumer>(new clang::ASTConsumer);
		}

		
		clang::DiagnosticConsumer& diagConsumer = m_CompilerInstance.getDiagnosticClient();
		diagConsumer.BeginSourceFile(m_CompilerInstance.getLangOpts(), &m_CompilerInstance.getPreprocessor());
		clang::ParseAST(m_CompilerInstance.getPreprocessor(), astConsumer.get(), m_CompilerInstance.getASTContext());
		diagConsumer.EndSourceFile();

		std::unique_ptr<clang::ASTContext> astContext(&m_CompilerInstance.getASTContext());
		_leakedPreprocessors.push_back(std::unique_ptr<clang::Preprocessor>(&m_CompilerInstance.getPreprocessor())); //necessary to retain tokens
		m_CompilerInstance.resetAndLeakASTContext();
		m_CompilerInstance.resetAndLeakPreprocessor();

		prepareDefaults(); //we wish to have a legal AST context at all times

		return astContext;
	}

	/*
	clang::CodeGenAction* ClangParser::emitLLVM(const char* szSourceCodeFilePath)
	{
		// create the compiler instance setup for this file as the main file
		prepareCompilerforFile(szSourceCodeFilePath);

		// Create an action and make the compiler instance carry it out
		clang::CodeGenAction *Act = new clang::EmitLLVMOnlyAction();
		if (!m_CompilerInstance.ExecuteAction(*Act))
			return NULL;

		return Act;
	}
	*/

	MatchAdapter::MatchAdapter(const MatchFunction& matchFunction) :
		_matchFunction(matchFunction)
	{
	}

	void MatchAdapter::run(const Result& result)
	{
		_matchFunction(result);
	}

	std::string mangle(const clang::NamedDecl* decl)
	{
		return mangle(decl, &decl->getASTContext());
	}

	std::string mangle(const clang::NamedDecl* decl, clang::ASTContext* context)
	{
		const std::unique_ptr<clang::MangleContext> mc(clang::ItaniumMangleContext::create(*context, context->getDiagnostics()));
		return mangle(decl, mc.get());
	}

	std::string mangle(const clang::NamedDecl* decl, clang::MangleContext* context)
	{
		std::string id;
		llvm::raw_string_ostream stream(id);
		if (auto constructorDecl = clang::dyn_cast<clang::CXXConstructorDecl>(decl))
		{
			context->mangleCXXCtor(constructorDecl, clang::Ctor_Complete, stream);
		}
		else if (auto destructorDecl = clang::dyn_cast<clang::CXXDestructorDecl>(decl))
		{
			context->mangleCXXDtor(destructorDecl, clang::Dtor_Complete, stream);
		}
		else
		{
			context->mangleCXXName(decl, stream);
		}
		stream.flush();
		return id;
	}
};
